﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Windows.Forms;

using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

using Graphene.Storing;

namespace Graphene
{
    public class DirectXPanel : Panel
    {
        private static readonly float THE_Xs_SIZE = 0.1f;

        public DirectXPanel(Model model)
        {
            this.model = model;
            this.beamsVertexBufferLock = new Object();  

            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);

            this.InitializeGraphics();
        }

        protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
        {
            device.Clear(ClearFlags.Target, System.Drawing.Color.FromArgb(25, 0, 40), 1.0f, 0);                                    
            PrepareCameraAndDevice();
            
            device.BeginScene();            

            device.VertexFormat = CustomVertex.PositionColored.Format;
            device.SetStreamSource(0, this.axesVertexBuffer, 0);
            device.DrawPrimitives(PrimitiveType.LineList, 0, 15);
            device.SetStreamSource(0, this.markersVertexBuffer, 0);
            device.DrawPrimitives(PrimitiveType.LineList, 0, this.nMarkerPrimitives);
            device.SetStreamSource(0, this.camerasVertexBuffer, 0);
            device.DrawPrimitives(PrimitiveType.LineList, 0, this.nCameraPrimitives);
            device.SetStreamSource(0, this.beamsVertexBuffer, 0);
            device.DrawPrimitives(PrimitiveType.LineList, 0, this.nBeamPrimitives);

            device.EndScene();


            device.Present();
            this.Invalidate();
        }

        private void AxesVertexBuffer_OnCreate(object sender, EventArgs e)
        {            
            CustomVertex.PositionColored[] axesVerts = new CustomVertex.PositionColored[30];
            axesVerts[0].Position = new Vector3(0, 0, 0); // axis X
            axesVerts[0].Color = System.Drawing.Color.FromArgb(255, 200, 200).ToArgb();
            axesVerts[1].Position = new Vector3(5, 0, 0);
            axesVerts[1].Color = System.Drawing.Color.FromArgb(255, 200, 200).ToArgb();
            axesVerts[2].Position = new Vector3(4.5f, 0.25f, 0.0f); // arrow on axis X
            axesVerts[2].Color = System.Drawing.Color.FromArgb(255, 200, 200).ToArgb();
            axesVerts[3].Position = new Vector3(5, 0, 0);
            axesVerts[3].Color = System.Drawing.Color.FromArgb(255, 200, 200).ToArgb();            
            axesVerts[4].Position = new Vector3(4.5f, -0.25f, 0.0f); // arrow on axis X
            axesVerts[4].Color = System.Drawing.Color.FromArgb(255, 200, 200).ToArgb();
            axesVerts[5].Position = new Vector3(5, 0, 0);
            axesVerts[5].Color = System.Drawing.Color.FromArgb(255, 200, 200).ToArgb();
            axesVerts[6].Position = new Vector3(4.5f, 0.0f, 0.25f); // arrow on axis X
            axesVerts[6].Color = System.Drawing.Color.FromArgb(255, 200, 200).ToArgb();
            axesVerts[7].Position = new Vector3(5, 0, 0);
            axesVerts[7].Color = System.Drawing.Color.FromArgb(255, 200, 200).ToArgb();
            axesVerts[8].Position = new Vector3(4.5f, 0.0f, -0.25f); // arrow on axis X
            axesVerts[8].Color = System.Drawing.Color.FromArgb(255, 200, 200).ToArgb();
            axesVerts[9].Position = new Vector3(5, 0, 0);
            axesVerts[9].Color = System.Drawing.Color.FromArgb(255, 200, 200).ToArgb();
            axesVerts[10].Position = new Vector3(0, 0, 0); // axis Y
            axesVerts[10].Color = System.Drawing.Color.FromArgb(200, 255, 200).ToArgb();
            axesVerts[11].Position = new Vector3(0, 5, 0);
            axesVerts[11].Color = System.Drawing.Color.FromArgb(200, 255, 200).ToArgb();
            axesVerts[12].Position = new Vector3(-0.25f, 4.5f, 0.0f); // arrow on axis Y
            axesVerts[12].Color = System.Drawing.Color.FromArgb(200, 255, 200).ToArgb();
            axesVerts[13].Position = new Vector3(0, 5, 0);
            axesVerts[13].Color = System.Drawing.Color.FromArgb(200, 255, 200).ToArgb();
            axesVerts[14].Position = new Vector3(0.25f, 4.5f, 0.0f); // arrow on axis Y
            axesVerts[14].Color = System.Drawing.Color.FromArgb(200, 255, 200).ToArgb();
            axesVerts[15].Position = new Vector3(0, 5, 0);
            axesVerts[15].Color = System.Drawing.Color.FromArgb(200, 255, 200).ToArgb();
            axesVerts[16].Position = new Vector3(0.0f, 4.5f, -0.25f); // arrow on axis Y
            axesVerts[16].Color = System.Drawing.Color.FromArgb(200, 255, 200).ToArgb();
            axesVerts[17].Position = new Vector3(0, 5, 0);
            axesVerts[17].Color = System.Drawing.Color.FromArgb(200, 255, 200).ToArgb();
            axesVerts[18].Position = new Vector3(0.0f, 4.5f, -0.25f); // arrow on axis Y
            axesVerts[18].Color = System.Drawing.Color.FromArgb(200, 255, 200).ToArgb();
            axesVerts[19].Position = new Vector3(0, 5, 0);
            axesVerts[19].Color = System.Drawing.Color.FromArgb(200, 255, 200).ToArgb();
            axesVerts[20].Position = new Vector3(0, 0, 0); // axis Z
            axesVerts[20].Color = System.Drawing.Color.FromArgb(200, 200, 255).ToArgb();
            axesVerts[21].Position = new Vector3(0, 0, 5);
            axesVerts[21].Color = System.Drawing.Color.FromArgb(200, 200, 255).ToArgb();
            axesVerts[22].Position = new Vector3(-0.25f, 0.0f, 4.5f); // arrow on axis Z
            axesVerts[22].Color = System.Drawing.Color.FromArgb(200, 200, 255).ToArgb();
            axesVerts[23].Position = new Vector3(0, 0, 5);
            axesVerts[23].Color = System.Drawing.Color.FromArgb(200, 200, 255).ToArgb();
            axesVerts[24].Position = new Vector3(0.25f, 0.0f, 4.5f); // arrow on axis Z
            axesVerts[24].Color = System.Drawing.Color.FromArgb(200, 200, 255).ToArgb();
            axesVerts[25].Position = new Vector3(0, 0, 5);
            axesVerts[25].Color = System.Drawing.Color.FromArgb(200, 200, 255).ToArgb();
            axesVerts[26].Position = new Vector3(0.0f, 0.25f, 4.5f); // arrow on axis Z
            axesVerts[26].Color = System.Drawing.Color.FromArgb(200, 200, 255).ToArgb();
            axesVerts[27].Position = new Vector3(0, 0, 5);
            axesVerts[27].Color = System.Drawing.Color.FromArgb(200, 200, 255).ToArgb();
            axesVerts[28].Position = new Vector3(0.0f, -0.25f, 4.5f); // arrow on axis Z
            axesVerts[28].Color = System.Drawing.Color.FromArgb(200, 200, 255).ToArgb();
            axesVerts[29].Position = new Vector3(0, 0, 5);
            axesVerts[29].Color = System.Drawing.Color.FromArgb(200, 200, 255).ToArgb();
            
            ((VertexBuffer)sender).SetData(axesVerts, 0, LockFlags.None);
        }

        public void MarkersVertexBuffer_OnCreate(object sender, EventArgs e)
        {
            Dictionary<string, MarkerInSpace> markers = this.model.GetMarkersFromThreeDProcessor();
            if (markers == null)
            {
                this.nMarkerPrimitives = 0;
                return;
            }

            this.nMarkerPrimitives = markers.Count * 3;
            CustomVertex.PositionColored[] pointVerts = new CustomVertex.PositionColored[this.nMarkerPrimitives * 2];
            int i = 0;
            foreach (MarkerInSpace marker in markers.Values)
            {
                int j = 6 * i;
                Vector3 markerPosition = marker.Position.ToVector3();
                pointVerts[j + 0].Position = markerPosition + new Vector3(THE_Xs_SIZE, 0, 0);
                pointVerts[j + 1].Position = markerPosition + new Vector3(-THE_Xs_SIZE, 0, 0);
                pointVerts[j + 2].Position = markerPosition + new Vector3(0, THE_Xs_SIZE, 0);
                pointVerts[j + 3].Position = markerPosition + new Vector3(0, -THE_Xs_SIZE, 0);
                pointVerts[j + 4].Position = markerPosition + new Vector3(0, 0, THE_Xs_SIZE);
                pointVerts[j + 5].Position = markerPosition + new Vector3(0, 0, -THE_Xs_SIZE);
                pointVerts[j + 0].Color = System.Drawing.Color.Yellow.ToArgb();
                pointVerts[j + 1].Color = System.Drawing.Color.Yellow.ToArgb();
                pointVerts[j + 2].Color = System.Drawing.Color.Yellow.ToArgb();
                pointVerts[j + 3].Color = System.Drawing.Color.Yellow.ToArgb();
                pointVerts[j + 4].Color = System.Drawing.Color.Yellow.ToArgb();
                pointVerts[j + 5].Color = System.Drawing.Color.Yellow.ToArgb();
                i++;
            }

            this.markersVertexBuffer.SetData(pointVerts, 0, LockFlags.None);
        }

        public void CamerasVertexBuffer_OnChange(object sender, EventArgs e)
        {
            float d = 3.0f;
            int nPrimitivesInACamera = 18;
            Microsoft.DirectX.Matrix rotationMatrix;
            Vector3 direction;
            Vector3 corner;
            Vector3 normalY;
            Vector3 normalZ;

            this.nCameraPrimitives = this.model.VideoSources.Count * nPrimitivesInACamera;

            CustomVertex.PositionColored[] camVerts = new CustomVertex.PositionColored[this.nCameraPrimitives];
            int i = 0;
            foreach (Graphene.Processing.VideoSource videoSource in this.model.VideoSources.Values)
            {
                int j = nPrimitivesInACamera * i;

                camVerts[j + 16].Position = videoSource.PositionInWorld.ToVector3();
                direction = new Vector3(d, 0, 0);
                normalY = new Vector3(0, 1, 0);
                normalZ = new Vector3(0, 0, 1);
                rotationMatrix = Microsoft.DirectX.Matrix.RotationZ(videoSource.AngleInWorld.X);
                rotationMatrix.Multiply(Microsoft.DirectX.Matrix.RotationY(videoSource.AngleInWorld.Y));
                direction.TransformCoordinate(rotationMatrix);
                normalY.TransformCoordinate(rotationMatrix);
                normalZ.TransformCoordinate(rotationMatrix);
                camVerts[j + 17].Position = videoSource.PositionInWorld.ToVector3() + direction;

                corner = new Vector3(direction.X, direction.Y, direction.Z);
                rotationMatrix = Microsoft.DirectX.Matrix.RotationAxis(normalY, videoSource.FOV.X / 2);
                rotationMatrix.Multiply(Microsoft.DirectX.Matrix.RotationAxis(normalZ, videoSource.FOV.Y / 2));           
                corner.TransformCoordinate(rotationMatrix);
                camVerts[j + 1].Position = videoSource.PositionInWorld.ToVector3() + corner;

                corner = new Vector3(direction.X, direction.Y, direction.Z);
                rotationMatrix = Microsoft.DirectX.Matrix.RotationAxis(normalY, -videoSource.FOV.X / 2);
                rotationMatrix.Multiply(Microsoft.DirectX.Matrix.RotationAxis(normalZ, videoSource.FOV.Y / 2));           
                corner.TransformCoordinate(rotationMatrix);
                camVerts[j + 3].Position = videoSource.PositionInWorld.ToVector3() + corner;

                corner = new Vector3(direction.X, direction.Y, direction.Z);
                rotationMatrix = Microsoft.DirectX.Matrix.RotationAxis(normalY, -videoSource.FOV.X / 2);
                rotationMatrix.Multiply(Microsoft.DirectX.Matrix.RotationAxis(normalZ, -videoSource.FOV.Y / 2));
                corner.TransformCoordinate(rotationMatrix);
                camVerts[j + 5].Position = videoSource.PositionInWorld.ToVector3() + corner;

                corner = new Vector3(direction.X, direction.Y, direction.Z);
                rotationMatrix = Microsoft.DirectX.Matrix.RotationAxis(normalY, videoSource.FOV.X / 2);
                rotationMatrix.Multiply(Microsoft.DirectX.Matrix.RotationAxis(normalZ, -videoSource.FOV.Y / 2));
                corner.TransformCoordinate(rotationMatrix);
                camVerts[j + 7].Position = videoSource.PositionInWorld.ToVector3() + corner;

                camVerts[j + 0].Position = videoSource.PositionInWorld.ToVector3();
                camVerts[j + 2].Position = videoSource.PositionInWorld.ToVector3();
                camVerts[j + 4].Position = videoSource.PositionInWorld.ToVector3();
                camVerts[j + 6].Position = videoSource.PositionInWorld.ToVector3();

                
                camVerts[j + 8].Position = camVerts[j + 1].Position; //ring
                camVerts[j + 9].Position = camVerts[j + 3].Position;
                camVerts[j + 10].Position = camVerts[j + 3].Position;
                camVerts[j + 11].Position = camVerts[j + 5].Position;
                camVerts[j + 12].Position = camVerts[j + 5].Position;
                camVerts[j + 13].Position = camVerts[j + 7].Position;
                camVerts[j + 14].Position = camVerts[j + 7].Position;
                camVerts[j + 15].Position = camVerts[j + 1].Position;                

                
                camVerts[j + 0].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 1].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 2].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 3].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 4].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 5].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 6].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 7].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 8].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 9].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 10].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 11].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 12].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 13].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 14].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 15].Color = System.Drawing.Color.Blue.ToArgb();
                camVerts[j + 16].Color = System.Drawing.Color.BlueViolet.ToArgb();
                camVerts[j + 17].Color = System.Drawing.Color.BlueViolet.ToArgb();
                i++;
            }

            this.camerasVertexBuffer.SetData(camVerts, 0, LockFlags.None);
        }

        public static bool LOFASZ = false;

        public void BeamsVertexBuffer_OnChange(object sender, EventArgs e)
        {
            if (LOFASZ)
            {
                return;
            }

            float d = 3.0f;
            int nPrimitivesInABeam = 2;
            this.nBeamPrimitives = this.model.ImageProcessors.Count * nPrimitivesInABeam + 12;
            CustomVertex.PositionColored[] beamVerts = new CustomVertex.PositionColored[this.nBeamPrimitives];

            int commonIndex = 0;
            for (int processorIndex = 0; processorIndex < this.model.ImageProcessors.Count; processorIndex++)
            {
                Graphene.Processing.MultiColorImageProcessor imageProcessor = this.model.ImageProcessors.ElementAt(processorIndex).Value;
                Graphene.Processing.VideoSource videoSource = (Graphene.Processing.VideoSource)imageProcessor.AssignedProcessorSource;
                Dictionary<string, MarkerInImage>.ValueCollection markers = ((Dictionary<string, MarkerInImage>)imageProcessor.ResultData).Values;
                for (int markerIndex = 0; markerIndex < markers.Count; markerIndex++)
                {                    
                    MarkerInImage marker = markers.ElementAt(markerIndex);
                    if (!marker.Exists)
                    {
                        continue;
                    }

                    beamVerts[commonIndex].Position = videoSource.PositionInWorld.ToVector3();
                    beamVerts[commonIndex++].Color = System.Drawing.Color.ForestGreen.ToArgb();

                    Vector3 direction = new Vector3(d, 0, 0);
                    Vector3 normalY = new Vector3(0, 1, 0);
                    Vector3 normalZ = new Vector3(0, 0, 1);
                    
                    Microsoft.DirectX.Matrix rotationMatrix = Microsoft.DirectX.Matrix.RotationZ(videoSource.AngleInWorld.X);
                    rotationMatrix.Multiply(Microsoft.DirectX.Matrix.RotationY(videoSource.AngleInWorld.Y));
                    direction.TransformCoordinate(rotationMatrix);
                    normalY.TransformCoordinate(rotationMatrix);
                    normalZ.TransformCoordinate(rotationMatrix);

                    PointReal2D beamDirection = new PointReal2D(-marker.Position.Y * videoSource.FOV.Y / 2, -marker.Position.X * videoSource.FOV.X / 2);
                    rotationMatrix = Microsoft.DirectX.Matrix.RotationAxis(normalZ, beamDirection.X);
                    rotationMatrix.Multiply(Microsoft.DirectX.Matrix.RotationAxis(normalY, beamDirection.Y));
                    direction.TransformCoordinate(rotationMatrix);
                    normalY.TransformCoordinate(rotationMatrix);
                    normalZ.TransformCoordinate(rotationMatrix);

                    beamVerts[commonIndex].Position = videoSource.PositionInWorld.ToVector3() + direction;
                    beamVerts[commonIndex++].Color = System.Drawing.Color.ForestGreen.ToArgb();
                }
            }

            if (Graphene.Processing.Processor3D.C2 != null)
            {
                beamVerts[commonIndex].Position = Graphene.Processing.Processor3D.O1;
                beamVerts[commonIndex++].Color = System.Drawing.Color.White.ToArgb();
                beamVerts[commonIndex].Position = Graphene.Processing.Processor3D.D1;
                beamVerts[commonIndex++].Color = System.Drawing.Color.White.ToArgb();
                beamVerts[commonIndex].Position = Graphene.Processing.Processor3D.O2;
                beamVerts[commonIndex++].Color = System.Drawing.Color.White.ToArgb();
                beamVerts[commonIndex].Position = Graphene.Processing.Processor3D.D2;
                beamVerts[commonIndex++].Color = System.Drawing.Color.White.ToArgb();
                beamVerts[commonIndex].Position = Graphene.Processing.Processor3D.C1;
                beamVerts[commonIndex++].Color = System.Drawing.Color.Red.ToArgb();
                beamVerts[commonIndex].Position = Graphene.Processing.Processor3D.C2;
                beamVerts[commonIndex++].Color = System.Drawing.Color.Red.ToArgb();
                beamVerts[commonIndex].Position = new Vector3();
                beamVerts[commonIndex++].Color = System.Drawing.Color.WhiteSmoke.ToArgb();
                beamVerts[commonIndex].Position = Graphene.Processing.Processor3D.CROSS;
                beamVerts[commonIndex++].Color = System.Drawing.Color.WhiteSmoke.ToArgb();

                //planes
                beamVerts[commonIndex].Position = Graphene.Processing.Processor3D.O1;
                beamVerts[commonIndex++].Color = System.Drawing.Color.YellowGreen.ToArgb();
                beamVerts[commonIndex].Position = Graphene.Processing.Processor3D.INTER2;
                beamVerts[commonIndex++].Color = System.Drawing.Color.YellowGreen.ToArgb();

                //planes
                beamVerts[commonIndex].Position = Graphene.Processing.Processor3D.O2;
                beamVerts[commonIndex++].Color = System.Drawing.Color.YellowGreen.ToArgb();
                beamVerts[commonIndex].Position = Graphene.Processing.Processor3D.INTER1;
                beamVerts[commonIndex++].Color = System.Drawing.Color.YellowGreen.ToArgb();
            }

            lock (beamsVertexBufferLock)
            {
                this.beamsVertexBuffer.SetData(beamVerts, 0, LockFlags.None);
            }
        }

        private void InitializeGraphics()
        {
            PresentParameters presentationParams = new PresentParameters();
            presentationParams.Windowed = true;
            presentationParams.SwapEffect = SwapEffect.Discard;
            device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentationParams);                      

            this.axesVertexBuffer = new VertexBuffer(
                typeof(CustomVertex.PositionColored),
                255,
                device,
                Usage.Dynamic | Usage.WriteOnly,
                CustomVertex.PositionColored.Format,
                Pool.Default);
            this.markersVertexBuffer = new VertexBuffer(
                typeof(CustomVertex.PositionColored),
                255,
                device,
                Usage.Dynamic | Usage.WriteOnly,
                CustomVertex.PositionColored.Format,
                Pool.Default);
            this.camerasVertexBuffer = new VertexBuffer(
                typeof(CustomVertex.PositionColored),
                255,
                device,
                Usage.Dynamic | Usage.WriteOnly,
                CustomVertex.PositionColored.Format,
                Pool.Default);
            this.beamsVertexBuffer = new VertexBuffer(
                typeof(CustomVertex.PositionColored),
                255,
                device,
                Usage.Dynamic | Usage.WriteOnly,
                CustomVertex.PositionColored.Format,
                Pool.Default);
            this.axesVertexBuffer.Created += new EventHandler(this.AxesVertexBuffer_OnCreate);
            this.markersVertexBuffer.Created += new EventHandler(this.MarkersVertexBuffer_OnCreate);
            this.camerasVertexBuffer.Created += new EventHandler(this.CamerasVertexBuffer_OnChange);
            this.beamsVertexBuffer.Created += new EventHandler(this.BeamsVertexBuffer_OnChange);            
            AxesVertexBuffer_OnCreate(this.axesVertexBuffer, null);
            MarkersVertexBuffer_OnCreate(this.markersVertexBuffer, null);
            CamerasVertexBuffer_OnChange(this.camerasVertexBuffer, null);
            BeamsVertexBuffer_OnChange(this.beamsVertexBuffer, null);
        }

        private void PrepareCameraAndDevice()
        {
            device.RenderState.Lighting = false;
            device.RenderState.CullMode = Cull.None;

            Vector3 pos = model.DXCameraPosition;
            Vector3 dir = model.DXCameraDirection;
            Vector3 lookAt = pos + dir;            
            device.Transform.View = Matrix.LookAtLH(pos, lookAt, new Vector3(0, 1, 0));
            device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, this.Width / this.Height, 1.0f, 100.0f);
        }

        private Model model;

        private Device device;        
        private VertexBuffer axesVertexBuffer;
        private VertexBuffer markersVertexBuffer;
        private VertexBuffer camerasVertexBuffer;
        private VertexBuffer beamsVertexBuffer;

        private Object beamsVertexBufferLock;

        private int nMarkerPrimitives;
        private int nCameraPrimitives;
        private int nBeamPrimitives;
    }
}
